/*
 * @Author: xinzheYu
 * @Create Time: 2017-09-08 18:57:36
 * @Last Modified By: xinzheYu
 * @Last Modified Time: 2017-09-08 18:57:42
 * @Filename: parse_metafile.c
 */
// parse_metafile.c
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "parse_metafile.h"
#include "sha1.h"
#include "dbg.h"


char *metafile_content = NULL;  // 保存种子文件的内容
long filesize;              // 保存种子文件的长度

int piece_length = 0;       // 每个piece的长度，通常为256kb即262114字节
char *pieces = NULL;        // 保存每个pieces的哈希值，每个哈希值为20字节
int pieces_length = 0;      // 缓冲区pieces的长度

int multi_file = 0;         // 指明为多还是单文件
char *file_name = NULL;     // 单：文件名 多：目录名
long long file_length = 0;  // 待下载文件长度
Files *files_head = NULL;    // 多文件种子需要存放各个文件的路径和 长度

unsigned char info_hash[20];    // 保存info_hash 的值，连接tracker和peer时使用
unsigned char peer_id[20];      // 保存peer_id 的值，连接peer时使用

Announce_list *announce_list_head = NULL; // 用于保存所有tracker服务器的url

int read_metafile(char *metafile_name)
{
    // 以二进制的方式只读方式读取文件
    FILE *fp;

    fp = fopen(metafile_name, "rb");
    check(fp != NULL, "open file error");

    // 获取种子文件的大小
    fseek(fp, 0, SEEK_END);
    filesize = ftell(fp);
    check(filesize != -1, "seek file error");

    metafile_content = (char *) malloc(filesize + 1);
    check(metafile_content != NULL, "malloc error");

    // 读取文件内容
    long i;
    fseek(fp, 0, SEEK_SET);
    for (i = 0; i < filesize; i++) {
        metafile_content[i] = fgetc(fp);
    }
    metafile_content[i] = '\0';

    fclose(fp);

#ifdef MYDEBUG
    printf("metafile size is: %ld\n", filesize);
#endif

    return 0;

error:
    return -1;
}

int find_keyword(char *keyword, long *position)
{
    long i;

    *position = -1;
    if (keyword == NULL) return 0;

    for (i = 0; i < filesize - strlen(keyword); i++) {
        if ( memcmp(&metafile_content[i], keyword, strlen(keyword)) == 0 ) {
            *position = i;
            return 1;
        }
    }

    return 0;
}

int read_announce_list()
{
    Announce_list *node = NULL;
    Announce_list *p = NULL;
    int len = 0;
    long i;

    if (find_keyword("13:announce-list", &i) == 0) {
        if (find_keyword("8:announce", &i) == 1) {
            i = i + strlen("8:announce");
            while (isdigit(metafile_content[i])) {
                len = len * 10 + (metafile_content[i] - '0');
                i++;
            }
            i++; // 跳过 ':'
        }

        node = (Announce_list *) malloc(sizeof(Announce_list));
        check(node != NULL, "malloc error");
        strncpy(node->announce, &metafile_content[i], len);
        node->announce[len] = '\0';
        node->next = NULL;
        announce_list_head = node;
    } else {
        i = i + strlen("13:announce-list");
        i++; // 跳过 l
        while (metafile_content[i] != 'e') {
            i++; // 跳过l
            while (isdigit(metafile_content[i])) {
                len = len * 10 + (metafile_content[i] - '0');
                i++;
            }
            if (metafile_content[i] == ':') i++;
            else return -1;

            // 只处理以http开头的tracker地址，不处理以udp开头地址
            if (memcmp(&metafile_content[i], "http", 4) == 0) {
                node = (Announce_list *) malloc(sizeof(Announce_list));
                check(node != NULL, "malloc error");
                strncpy(node->announce, &metafile_content[i], len);
                node->announce[len] = '\0';
                node->next = NULL;

                if (announce_list_head == NULL)
                    announce_list_head = node;
                else {
                    p = announce_list_head;
                    while (p->next != NULL) p = p->next;
                    p->next = node;
                }
            }
            i = i + len;
            len = 0;
            i++; // 跳过e
            if (i >= filesize) return -1;
        }// while end
    }

#ifdef MYDEBUG
    p = announce_list_head;
    while (p != NULL) {
        printf("%s", p->announce);
        p = p->next;
    }
#endif

    return 0;

error:
    return -1;
}

int add_an_announce(char *url)
{
    Announce_list *p = announce_list_head, *q;

    // 若参数指定的url在tracker列表中已存在，则无需添加
    while (p != NULL) {
        if (strcmp(p->announce, url) == 0) break;
        p = p->next;
    }
    if (p != NULL) return 0;

    q = (Announce_list *) malloc(sizeof(Announce_list));
    strcpy(q->announce, url);
    q->next = NULL;

    p = announce_list_head;
    if (p == NULL) { announce_list_head = q; return 1; }

    while (p->next != NULL) p = p->next;
    p->next = q;

    return 1;
}

int is_mutli_files()
{
    long i;

    if (find_keyword("5:files", &i) == 1) {
        multi_file = 1;
        return 1;
    }

#ifdef MYDEBUG
    printf("is_mutli_files: %d\n", multi_file);
#endif

    return 0;
}

int get_piece_length()
{
    long i;

    if (find_keyword("12:piece length", &i) == 1) {
        i = i + strlen("12:piece length"); // 跳过"12:piece length"
        i++; // 跳过'i'
        while (metafile_content[i] != 'e') {
            piece_length = piece_length * 10 + (metafile_content[i] - '0');
            i++;
        }
    } else {
        return -1;
    }

#ifdef MYDEBUG
    printf("piece length: %d\n", piece_length);
#endif

    return 0;
}

int get_pieces()
{
    long i;

    if (find_keyword("6:pieces", &i) == 1) {
        i = i + 8; // 跳过 "6:pieces"
        while (metafile_content[i] != ':') {
            pieces_length = pieces_length * 10 + (metafile_content[i] - '0');
            i++;
        }
        i++;
        pieces = (char *) malloc(pieces_length + 1);
        check(pieces != NULL, "malloc error");
        memcpy(pieces, &metafile_content[i], pieces_length);
        pieces[pieces_length] = '\0';
    } else {
        return -1;
    }

#ifdef MYDEBUG
    printf("get_pieces ok\n");
#endif

    return 0;

error:
    return -1;
}

int get_file_name()
{
    long i;
    int count = 0;

    if (find_keyword("4:name", &i) == 1) {
        i += 6; // 跳过 "4:name"
        while (metafile_content[i] != ':') {
            count = count * 10 + (metafile_content[i] - '\0');
            i++;
        }
        i++; // 跳过':'
        file_name = (char *) malloc(count + 1);
        memcpy(file_name, &metafile_content[i], count);
        file_name[count] = '\0';
    } else {
        return -1;
    }

#ifdef MYDEBUG
    printf("file_name:%s\n", file_name);
#endif

    return 0;
}

int get_file_length()
{
    long i;

    if (is_mutli_files() == 1) {
        if (files_head == NULL) get_files_length_path();
        Files *p = files_head;
        while (p != NULL) { file_length += p->length; p = p->next; }
    } else {
        if (find_keyword("6:length", &i) == 1) {
            i = i + 8;
            i++;
            while (metafile_content[i] != 'e') {
                file_length = file_length * 10 + (metafile_content[i] - '0');
                i++;
            }
        }
    }

#ifdef MYDEBUG
    printf("file_length: %lld\n", file_length);
#endif

    return 0;
}


int get_files_length_path()
{
    long i;
    int length;
    int count;
    Files *node = NULL;
    Files *p = NULL;

    if (is_mutli_files() != 1) {
        return 0;
    }

    for (i = 0; i < filesize-8; i++) {
        if (memcmp(&metafile_content[i], "6:length", 8) == 0) {
            i = i + 8;
            i++;
            length = 0;
            while (metafile_content[i] != 'e') {
                length = length * 10 + (metafile_content[i] - '0');
                i++;
            }
            node = (Files *) malloc(sizeof(Files));
            node->length = length;
            node->next = NULL;
            if (files_head == NULL) {
                files_head = node;
            } else {
                p = files_head;
                while (p->next != NULL) p = p->next;
                p->next = node;
            }
        }
        if (memcmp(&metafile_content[i], "4:path", 6) == 0) {
            i = i + 6;
            i++;
            count = 0;
            while (metafile_content[i] != ':') {
                count = count * 10 + (metafile_content[i] - '0');
                i++;
            }
            i++;
            p = files_head;
            while (p->next != NULL) p = p->next;
            memcpy(p->path, &metafile_content[i], count);
            *(p->path + count) = '\0';
        }
    }

    return 0;
}

int get_info_hash()
{
    int push_pop = 0;
    long i, begin, end;

    if (metafile_content == NULL) return -1;

    // BEGIN 的值表示关键字"4:info" 对应值得起始下标
    if (find_keyword("4:info", &i) == 1)
        begin = i + 6;
    else return -1;

    i = i + 6;
    for (; i < filesize;) {
        if (metafile_content[i] == 'd') {
            push_pop++;
            i++;
        } else if (metafile_content[i] == 'l') {
            push_pop++;
            i++;
        } else if (metafile_content[i] == 'i') {
            i++; // 跳过i
            if (i == filesize) return -1;
            while (metafile_content[i] != 'e') {
                if ( (i + 1) == filesize )  return -1;
                else i++;
            }
        } else if ((metafile_content[i] >= '0') && (metafile_content[i] <= '9')) {
            int number = 0;
            while ( (metafile_content[i] >= '0') && (metafile_content[i] <= '9')) {
                number = number * 10 + metafile_content[i] - '0';
                i++;
            }
            i++; // 跳过 ':'
            i = i + number;
        } else if (metafile_content[i] == 'e') {
            push_pop--;
            if (push_pop == 0) { end = i; break; }
            else i++;
        } else {
            return -1;
        }
    }
    if (i == filesize) return -1;
    SHA1_CTX context;
    SHA1Init(&context);
    SHA1Update(&context, (const unsigned char*) &metafile_content[begin], end-begin + 1);
    SHA1Final(info_hash, &context);

#ifdef MYDEBUG
    printf("info_hash: ");
    for (i = 0; i < 20; i++)
        printf("%.2x ", info_hash[i]);
    printf("\n");
#endif

    return 0;
}

int get_peer_id()
{
    // 设置产生随机数的种子
    srand(time(NULL));
    // 使用rand函数生成一个随机数，并使用该随机数来构造peer_id
    // peer_id 前 8 位固定为-TT1000-
    sprintf((char *)peer_id, "-TT1000-%12d", rand());

#ifdef MYDEBUG
    printf("peer_id: %s\n", peer_id);
#endif

    return 0;
}

void release_memory_in_parse_metafile()
{
    Announce_list *p;
    Files *q;

    if (metafile_content != NULL) free(metafile_content);
    if (file_name != NULL) free(file_name);
    if (pieces != NULL) free(pieces);

    while (announce_list_head != NULL) {
        p = announce_list_head;
        announce_list_head = announce_list_head;
        free(p);
    }

    while (files_head != NULL) {
        q = files_head;
        files_head = files_head->next;
        free(q);
    }
}

int parse_metafile(char *metafile)
{
    int ret;

    // 读取种子文件
    ret = read_metafile(metafile);
    check(ret >= 0, "error");

    // 从种子文件中获取tracker服务器地址
    ret = read_announce_list();
    check(ret >= 0, "error");

    // 判断是否为多重文件
    ret = is_mutli_files();
    check(ret >= 0, "error");

    // 获取每个piece的长度
    ret = get_piece_length();
    check(ret >= 0, "error");

    // 读取各个piece的哈希值
    ret = get_pieces();
    check(ret >= 0, "error");

    // 获取要下载的文件名， 对于多文件的种子，获取的是目录名
    ret = get_file_name();
    check(ret >= 0, "error");

    // 对于多文件的种子，获取各个待下载的文件的路径和长度
    ret = get_files_length_path();
    check(ret >= 0, "error");

    // 获取待下载文件的总长度
    ret = get_file_length();
    check(ret >= 0, "error");

    // 获取info_hash，生成peer_id
    ret = get_info_hash();
    check(ret >= 0, "error");

    ret = get_peer_id();
    check(ret >= 0, "error");

error:
    return -1;
}

